package com.kwin.demo.server.framework.util.datadic.filter;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.kwin.demo.server.framework.util.Safes;
import com.kwin.demo.server.framework.util.datadic.annotation.DataDict;
import com.kwin.demo.server.framework.util.datadic.annotation.DictField;
import com.kwin.demo.server.framework.util.datadic.component.DictService;
import com.kwin.demo.server.framework.util.datadic.handler.Test;
import com.kwin.demo.server.framework.util.datadic.handler.Test2;
import com.kwin.demo.server.framework.util.datadic.model.DictDetail;
import com.kwin.demo.server.framework.util.datadic.model.DictType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据字典翻译切面
 * @author kwin
 * @Date 2022/2/14 18:31
 **/
@Aspect
@Slf4j
public class DictAspect {

    @Resource
    private DictService dictService;

    @Pointcut("@annotation(dataDic)")
    public void dataDict(DataDict dataDic) {

    }

    @Around("@annotation(dataDic)")
    public Object doAround(ProceedingJoinPoint pjp, DataDict dataDic) throws Throwable {
        Object result = pjp.proceed();

        Class returnType = getReturnClass(pjp);

        // 返回数据类型
        if (returnType == null || returnType.isPrimitive() || isWrapClass(returnType) || String.class.isAssignableFrom(returnType)){
            return result;
        }

        // 解析字典类型
        Set<String> dictCodes = parseDictCode(returnType, Sets.newHashSet());

        // 查询字典,redis
        Map<String, Map<String, String>> dictMap = convertDataDictMap(dictCodes);


        // 翻译字典
        transDict(returnType, result, dictMap);

        return result;
    }


    private Class getReturnClass(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            // 被切的方法
            Method method = methodSignature.getMethod();
            // 返回类型
            Class<?> methodReturnType = method.getReturnType();

            return methodReturnType;
        }
        return null;
    }

    /**
     * 是否基本数据类型的包装类，暂留，说不好有用
     * @param clz
     * @return
     */
    private static boolean isWrapClass(Class clz) {
        try {
            return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 解析需要翻译的数据字典
     * @param cla 解析的类
     * @param traceClzz 已解析的类，防止循环解析
     * @return
     */
    private static Set<String> parseDictCode(Class cla, Set<Class> traceClzz) {
        Set<String> set = Sets.newHashSet();
        if(cla.isPrimitive() || isWrapClass(cla) || String.class.isAssignableFrom(cla)) {
            return set;
        }
        if(Map.class.isAssignableFrom(cla)) {
            return set;
        }

        Field[] fields = cla.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(DictField.class)) {
                DictField dictField = field.getAnnotation(DictField.class);
                set.add(dictField.dictCode());
                continue;
            }
            Class fieldType = getFieldTypeOrArgumentType(field);
            if (fieldType == null || fieldType.isPrimitive() || isWrapClass(fieldType) || String.class.isAssignableFrom(fieldType)){
                continue;
            }
            if(Map.class.isAssignableFrom(cla)){
                continue;
            }
            if(!traceClzz.contains(fieldType)) {
                traceClzz.add(fieldType);
                set.addAll(parseDictCode(fieldType, traceClzz));
            }

        }
        return set;
    }

    /**
     * 查询并转换字典 <类型,<编码，描述>>
     * @param dictCodes
     * @return
     */
    private Map<String, Map<String, String>> convertDataDictMap(Set<String> dictCodes) {
        Map<String, Map<String, String>> retMap = Maps.newHashMap();
        if(CollectionUtils.isEmpty(dictCodes)) {
            return retMap;
        }
        List<DictType> dictTypes = dictService.findByDictCodes(dictCodes);
        Map<String, List<DictDetail>> type2DetailsMap = Safes.of(dictTypes).stream().collect(Collectors.toMap(DictType::getCode, DictType::getItems));
        Safes.of(type2DetailsMap).forEach((key, val) -> {
            Map<String, String> detailMap = Maps.newHashMap();
            retMap.put(key, detailMap);
            detailMap.putAll(Safes.of(val).stream().collect(Collectors.toMap(DictDetail::getCode, DictDetail::getName)));
        });
        return retMap;
    }

    /**
     * 获取field的类型，如果是数组或集合，则返回参数的类型
     * @param field
     * @return
     */
    private static Class getFieldTypeOrArgumentType(Field field) {
        Class fieldType = field.getType();

        if(fieldType.isArray()) {
            return getArrayArgumentType(fieldType.getComponentType());
        }

        if (Collection.class.isAssignableFrom(fieldType)) {
            if(field.getGenericType() instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType)field.getGenericType();
                return getArgumentType(parameterizedType);
            }
        }
        return fieldType;
    }


    /**
     * 数组or多层数组
     * @param type
     * @return
     */
    private static Class getArrayArgumentType(Class type) {
        if(type.isArray()) {
            return getArrayArgumentType(type.getComponentType());
        }
        // 集合的数组不支持 eg:List<String>[]
        if(Collection.class.isAssignableFrom(type)) {
            return null;
        }
        return type;
    }

    /**
     * 集合or多层集合（最内层为类or数组）
     * @param parameterizedType
     * @return
     */
    private static Class getArgumentType(ParameterizedType parameterizedType) {
        if(parameterizedType.getActualTypeArguments()[0] instanceof ParameterizedType) {
            return getArgumentType((ParameterizedType)parameterizedType.getActualTypeArguments()[0]);
        }
        Class type = (Class<?>)parameterizedType.getActualTypeArguments()[0];
        // 支持集合元素是数组的场景
        return getArrayArgumentType(type);
    }

    /**
     * 翻译字典
     * @param cla
     * @param obj
     * @param dictMap
     * @throws Exception
     */
    private static void transDict(Class cla, Object obj, Map<String, Map<String, String>> dictMap)
        throws Exception {
        if(cla.isPrimitive() || isWrapClass(cla) || String.class.isAssignableFrom(cla)) {
            return;
        }
        if(Map.class.isAssignableFrom(cla)){
            return;
        }
        if(obj == null) {
            return;
        }

        if(cla.isArray()) {
            Object[] objects = (Object[]) obj;
            for(Object object : objects ) {
                transDict(object.getClass(), object, dictMap);
            }
            return;
        }

        if(Collection.class.isAssignableFrom(cla)) {
            Collection colles = (Collection) obj;
            for(Object coll : colles) {
                transDict(coll.getClass(), coll, dictMap);
            }
            return;
        }

        Field[] fields = cla.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(DictField.class)) {
                DictField dictField = field.getAnnotation(DictField.class);
                Field sourceField = cla.getDeclaredField(dictField.sourceField());
                sourceField.setAccessible(true);
                Object code = sourceField.get(obj);
                Map<String, String> codeMap = dictMap.get(dictField.dictCode());
                if(codeMap != null && code != null) {
                    String name = codeMap.get(code.toString());
                    field.setAccessible(true);
                    field.set(obj, name);
                }
                continue;
            }
            Class fieldType = getFieldTypeOrArgumentType(field);
            if (fieldType.isPrimitive() || isWrapClass(fieldType) || String.class.isAssignableFrom(fieldType)){
                continue;
            }
            if(Map.class.isAssignableFrom(cla)){
                continue;
            }
            field.setAccessible(true);
            Object fieldObj = field.get(obj);
            if(fieldObj != null) {
                transDict(fieldObj.getClass(), fieldObj, dictMap);
            }
        }
    }

    public static void main(String[] args) throws Exception {

       /* List<List<Test2>> aa = new ArrayList<>();
        Test2[] test2s = new Test2[3];
        for(int i = 0; i < 3; i++) {
            List<Test2> list =  new ArrayList<>();
            for(int j = 0; j < 2; j++) {
                Test2 test2 = new Test2();
                test2.setTest(null);
                test2.setEe(String.valueOf(j+1));
                List<Test> list2 =  new ArrayList<>();
                for(int h = 1; h < 3 ; h++) {
                    Test te = new Test();
                    te.setAa(String.valueOf(h));
                    te.setBb(111);
                    list2.add(te);
                }
                test2.setTest2(list2);
                list.add(test2);
            }
            aa.add(list);

            test2s[i] = list.get(0);
        }
        Test test = new Test();
        test.setAa("4");
        test.setEe(aa);
        test.setArray(test2s);
        test.setCc(2);

//        Set<String> str = boDict(test.getClass(), new HashSet<>());
        Map<String, Map<String, String>> dicMap = new HashMap<>();
        Map<String, String> aacode = new HashMap<>();
        aacode.put("1", "aa111");
        aacode.put("2", "aa222");
        aacode.put("3", "aa333");
        aacode.put("4", "aa444");
        Map<String, String> eecode = new HashMap<>();
        eecode.put("1", "ee111");
        eecode.put("2", "ee222");
        eecode.put("3", "ee333");
        eecode.put("4", "ee444");

        Map<String, String> ccCode = new HashMap<>();
        ccCode.put("1", "cc111");
        ccCode.put("2", "cc222");
        ccCode.put("3", "cc333");
        ccCode.put("4", "cc444");

        dicMap.put("aa", aacode);
        dicMap.put("ee", eecode);
        dicMap.put("cc", ccCode);

        transDict(test.getClass(), test, dicMap);
        System.out.println(test);*/

   /*     SPECIAL_BOX_CLASS.forEach(key -> {

            System.out.println(key + " : " + isWrapClass(key));
        });*/
        System.out.println(String.class.isAssignableFrom(String.class));
    }
}
